package net.cohoivang.web;

import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.LinkedList;
import com.mysql.jdbc.CommunicationsException;

public class OurPrepareStatement extends OurStatementBase{
	
	private final OurConnection conn;
	private PreparedStatement ps; 	
	private final String sql;
	private final Integer options;
	
	private abstract class ParamSetter {
		final int i;
		ParamSetter(int fieldIndex) {
			this.i = fieldIndex;
		}
		abstract void doParam(PreparedStatement ps) throws SQLException;
	}
	private LinkedList<ParamSetter> setters = new LinkedList<OurPrepareStatement.ParamSetter>();
	private final LinkedList<LinkedList<ParamSetter>> laterSetters = new LinkedList<LinkedList<ParamSetter>>();	
	
	public OurPrepareStatement(OurConnection conn, String sql) {
		this.conn = conn;
		this.sql = sql;
		this.options = null;
	}
	
	public OurPrepareStatement(OurConnection conn, String sql, int options) {
		this.conn = conn;
		this.sql = sql;
		this.options = options;
	}
	
	public int executeUpdate() throws SQLException {
		try {
			if (ps == null) {
				ps = createPs();
			}
			return executeUpdateAcutal(ps, setters, sql, true);
		} catch (CommunicationsException ex) {
			System.out.println("SQLPreparedUpdateRetry: " + sql);
			this.conn.reopenConnection();
			ps = createPs();
			return executeUpdateAcutal(ps, setters, sql, true);
		}
	}
	@Override
	public void executeUpdateBatch(OurConnection oc, int batchSize)
			throws SQLException {
		PreparedStatement ps = null;
		while (batchSize > 0) {
			LinkedList<ParamSetter> executingSetters;
			synchronized (laterSetters) {
				if (laterSetters.isEmpty()) {
					break;
				}
				executingSetters = laterSetters.removeFirst();
				batchSize --;				
			}
			
			try {
				if (ps == null) {
					ps = createPs(oc);
					System.out.println("SQLPreparedUpdateBatch: "+ head(sql) + ": " + batchSize);
				}
				executeUpdateAcutal(ps, executingSetters, sql, true);
			} catch (CommunicationsException ex) {
				System.out.println("SQLPreparedUpdateBatchRetry: " + sql);
				oc.reopenConnection();
				ps = createPs(oc);
				executeUpdateAcutal(ps, executingSetters, sql, false);
			}
			catch (Exception ex) {
				System.out.println("execute Update Batch BBBBBBBBBBBBBBBBB");	
				oc.reopenConnection();
				ps = createPs(oc);
				executeUpdateAcutal(ps, executingSetters, sql, false);
			}	
		}
		if (ps != null) {
			ps.close();
		}
	}
	
	private int executeUpdateAcutal(PreparedStatement ps, 
			LinkedList<ParamSetter> executingSetters, String sql, boolean logging) throws SQLException {
		for (ParamSetter setter : executingSetters) {
			setter.doParam(ps);
		} 
		
		if (logging) {
			System.out.println("SQLPreparedUpdate: " + head(sql));
		}
		
		int result = ps.executeUpdate();
		return result;
	}
	
	@Override
	public void close() throws SQLException {
		if (ps != null) {
			ps.close();
		}
	}
	
	public ResultSet getGenerateKeys() throws SQLException {
		if (ps == null) {
			return null;
		}
		return ps.getGeneratedKeys();
	}
	
	private PreparedStatement createPs() throws SQLException {
		return createPs(conn);
	}
	
	private PreparedStatement createPs(OurConnection oc) throws SQLException {
		if (options == null) {
			return oc.prepareRealStatement(sql);
		} else {
			return oc.prepareRealStatement(sql, options);
		}		
	}
	
	public ResultSet executeQuery() throws SQLException {
		try {
			if (ps == null) {
				ps = createPs();
			}
			for (ParamSetter setter : setters) {
				setter.doParam(ps);
			}
			System.out.println("SQLPreparedQuery:" + head(sql));
			ResultSet rs = ps.executeQuery();
			setters.clear();
			return rs;			     
		} catch (CommunicationsException ex) {
			System.out.println("SQLPreparedQueryRetry: " + sql);
			conn.reopenConnection();
			ps = createPs();
			for (ParamSetter setter : setters) {
				setter.doParam(ps);
			}
			setters.clear();
			return ps.executeQuery();
		}
	}
	
	public void executeUpdateLater() {
		synchronized (laterSetters) {
			laterSetters.add(setters);
			setters = new LinkedList<OurPrepareStatement.ParamSetter>();
		}
		TradingDbThread.instance.add(this);
	}
	static String head(String s) {
		if (s.length() < 40) {
			return s;
		}
		return s.substring(0, 40);
	}
	
	private class BooleanSetter extends ParamSetter {

		private boolean b;
		BooleanSetter(int i, boolean b) {
			super(i);
			this.b = b;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {
			ps.setBoolean(i, b);
		}		
	}
	
	public void setBoolean(int i, boolean b) {
		setters.add(new BooleanSetter(i, b));
	}
	
	private class DateSetter extends ParamSetter {
		private Date d;
		DateSetter(int i, Date d) {
			super(i);
			this.d = d;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {
			// TODO Auto-generated method stub
			ps.setDate(i, d);
		}	
	}
	
	public void setDate(int i, Date d) {
		setters.add(new DateSetter(i, d));
	}
	
	private class DoubleSetter extends ParamSetter {
		private double d;
		DoubleSetter(int i, double d) {
			super(i);
			this.d = d;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {
			// TODO Auto-generated method stub
			ps.setDouble(i, d);
		}		
	}
	
	public void setDouble(int i, double d) {
		setters.add(new DoubleSetter(i, d));
	}
	
	private class FloatSetter extends ParamSetter {
		private float f;
		FloatSetter(int i, float f) {
			super(i);
			this.f = f;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {			
			ps.setFloat(i, f);
		}		
	}
	
	public void setFloat(int i, float f) {
		setters.add(new FloatSetter(i, f));
	}
	
	private class IntSetter extends ParamSetter {
		private int value;
		IntSetter(int i, int value) {
			super(i);
			this.value = value;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {			
			ps.setInt(i, value);
		}		
	}
	
	public void setInt(int i, int v) {
		setters.add(new IntSetter(i, v));
	}
	
	private class LongSetter extends ParamSetter {
		private long v;
		LongSetter(int i, long v) {
			super(i);
			this.v = v;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {			
			ps.setLong(i, v);
		}		
	}	
	
	public void setLong(int i, long v) {
		setters.add(new LongSetter(i, v));
	}
	
	private class StringSetter extends ParamSetter {
		private String str;
		StringSetter(int i, String str) {
			super(i);
			this.str = str;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {
			ps.setString(i, str);
		}
	}
	
	public void setString(int i, String str) {
		setters.add(new StringSetter(i, str));
	}
	
	private class TimeSetter extends ParamSetter {
		private Time v;
		TimeSetter(int i, Time v) {
			super(i);
			this.v = v;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {
			ps.setTime(i, this.v);
		}		
	}
	
	public void setTime(int i, Time v) {
		setters.add(new TimeSetter(i, v));
	}
	
	private class TimestamSetter extends ParamSetter {
		private Timestamp ts;
		TimestamSetter(int i, Timestamp ts) {
			super(i);
			this.ts = ts;
		}
		@Override
		void doParam(PreparedStatement ps) throws SQLException {
			ps.setTimestamp(i, ts);
		}
	}
	
	public void setTimeStamp(int i, Timestamp ts) {
		setters.add(new TimestamSetter(i, ts));
	}
}
